package cn.skyquant.quant4j.jforex.strategy.jingqi;

import cn.skyquant.quant4j.jforex.sdk.strategy.InstrumentEntity;
import cn.skyquant.quant4j.sdk.util.constant.EAConstants;
import com.dukascopy.api.*;
import com.dukascopy.api.feed.IBarFeedListener;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Library("quant4j-jforex-sdk.jar;quant4j-sdk.jar;")
public class JingQi implements IStrategy, IBarFeedListener {

    protected IEngine engine;
    protected IConsole console;
    protected IHistory history;
    protected IContext context;
    protected IIndicators indicators;
    protected IAccount account;
    protected IDataService dataService;

    @Configurable("交易品种")
    public Instrument instrument = Instrument.EURUSD;

    @Configurable("开单倍率(最小仓位几倍)")
    public int amount = 1;

    @Configurable("运行间隔时间")
    public Period period = Period.ONE_MIN;

    @Configurable("操作方法")
    public JingQiType type = JingQiType.stopOrder;

    @Configurable("多单:STOP价格↓下限(0表示无下限)")
    public double buy_stop_lower = 0;

    @Configurable("多单:STOP价格↑上限(0表示无上限)")
    public double buy_stop_upper = 0;

    @Configurable("多单:limit价格↓下限(0表示无下限)")
    public double buy_limit_lower = 0;

    @Configurable("多单:limit价格↑上限(0表示无上限)")
    public double buy_limit_upper = 0;

    @Configurable("空单:STOP价格↓下限(0表示无下限)")
    public double sell_stop_lower = 0;

    @Configurable("空单:STOP价格↑上限(0表示无上限)")
    public double sell_stop_upper = 0;

    @Configurable("空单:limit价格↓下限(0表示无下限)")
    public double sell_limit_lower = 0;

    @Configurable("空单:limit价格↑上限(0表示无上限)")
    public double sell_limit_upper = 0;

    @Configurable("止盈具体点值(1.125-1.126 填0.001)")
    public double tp_pip = 0.001;

    @Configurable("多单止损价(0表示不设置止损)")
    public double buy_sl = 1.115;

    @Configurable("空单止损价(0表示不设置止损)")
    public double sell_sl = 1.212;

    @Configurable("STOP间隔点值(EURUSD 5个点=0.0005)")
    public double jiange_stop = 0.005;

    @Configurable("STOP挂单数量(0表示不挂stop)")
    public int stop_num = 5;

    @Configurable("LIMIT间隔点值(DEU.IDX 10个点=10)")
    public double jiange_limit = 0.005;

    @Configurable("LIMIT挂单数量(0表示不挂limit)")
    public int limit_num = 5;

    @Configurable("挂单时间(分钟)")
    public int order_time = 60;


    @Configurable("控制:计算隔夜利息")
    public boolean overnight = true;
    @Configurable("控制:固定挂单数量")
    public boolean fixed_num = true;

    @Configurable("日志:订单提交")
    public boolean log_submit = false;
    @Configurable("日志:订单成交")
    public boolean log_fill = false;
    @Configurable("日志:隔夜利息")
    public boolean log_overnight = false;
    @Configurable("日志:订单平仓")
    public boolean log_close = false;
    @Configurable("日志:订单取消")
    public boolean log_cancel = false;

    InstrumentEntity ie;

    private static final String LABEL_PRE = "J";

    double _buy_stop_lower;
    double _buy_stop_upper;
    double _buy_limit_lower;
    double _buy_limit_upper;
    double _sell_stop_lower;
    double _sell_stop_upper;
    double _sell_limit_lower;
    double _sell_limit_upper;


    double[] buy_stop_s;
    double[] buy_limit_s;
    double[] sell_stop_s;
    double[] sell_limit_s;

    final double percent_max = 0.1;//默认在10%上下范围内做单

    @Override
    public void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.context = context;
        this.indicators = context.getIndicators();
        this.account = context.getAccount();
        this.dataService = context.getDataService();
        ie = new InstrumentEntity(instrument);
        Set<Instrument> instrumentSet = new HashSet<>();
        instrumentSet.add(instrument);
        context.setSubscribedInstruments(instrumentSet);
        context.subscribeToBarsFeed(instrument, period, OfferSide.ASK, this);
        ITick tick = history.getLastTick(instrument);
        if (tp_pip < tick.getAsk() * 0.0003) {
            console.getErr().println("止盈点值太小，请检查止盈点值，不能小于交易品种当前价格"+tick.getAsk()+"的万分之三");
            context.stop();
            return;

        }
        if (jiange_limit < tick.getAsk() * 0.0003) {
            console.getErr().println("LIMIT间隔点值太小，请检查LIMIT间隔点值，不能小于当前价格"+tick.getAsk()+"的万分之三");
            context.stop();
            return;
        }
        if (jiange_stop < tick.getAsk() * 0.0003) {
            console.getErr().println("STOP间隔点值太小，请检查STOP间隔点值，不能小于当前价格"+tick.getAsk()+"的万分之三");
            context.stop();
            return;
        }
        if (buy_limit_lower == 0) {
            _buy_limit_lower = tick.getBid() - tick.getBid() * percent_max;
        } else {
            _buy_limit_lower = buy_limit_lower;
        }
        if (buy_limit_upper == 0) {
            _buy_limit_upper = tick.getAsk() + tick.getAsk() * percent_max;
        } else {
            _buy_limit_upper = buy_limit_upper;
        }
        if (_buy_limit_upper != 0 && _buy_limit_lower != 0 && _buy_limit_upper < _buy_limit_lower) {
            console.getErr().println("多单limit上限[" + _buy_limit_upper + "]低于多单limit下限[" + _buy_limit_lower + "]，请重新设置参数");
            context.stop();
            return;
        }

        if (buy_stop_lower == 0) {
            _buy_stop_lower = tick.getBid() - tick.getBid() * percent_max;
        } else {
            _buy_stop_lower = buy_stop_lower;
        }
        if (buy_stop_upper == 0) {
            _buy_stop_upper = tick.getAsk() + tick.getAsk() * percent_max;
        } else {
            _buy_stop_upper = buy_stop_upper;
        }

        if (_buy_stop_upper != 0 && _buy_stop_lower != 0 && _buy_stop_upper < _buy_stop_lower) {
            console.getErr().println("多单stop上限[" + _buy_stop_upper + "]低于多单stop下限[" + _buy_stop_lower + "]，请重新设置参数");
            context.stop();
            return;
        }

        if (sell_limit_lower == 0) {
            _sell_limit_lower = tick.getBid() - tick.getBid() * percent_max;
        } else {
            _sell_limit_lower = sell_limit_lower;
        }
        if (sell_limit_upper == 0) {
            _sell_limit_upper = tick.getAsk() + tick.getAsk() * percent_max;
        } else {
            _sell_limit_upper = sell_limit_upper;
        }

        if (_sell_limit_upper != 0 && _sell_limit_lower != 0 && _sell_limit_upper < _sell_limit_lower) {
            console.getErr().println("空单limit上限[" + _sell_limit_upper + "]低于空单limit下限[" + _sell_limit_lower + "]，请重新设置参数");
            context.stop();
            return;
        }

        if (sell_stop_lower == 0) {
            _sell_stop_lower = tick.getBid() - tick.getBid() * percent_max;
        } else {
            _sell_stop_lower = sell_stop_lower;
        }
        if (sell_stop_upper == 0) {
            _sell_stop_upper = tick.getAsk() + tick.getAsk() * percent_max;
        } else {
            _sell_stop_upper = sell_stop_upper;
        }

        if (_sell_stop_upper != 0 && _sell_stop_lower != 0 && _sell_stop_upper < _sell_stop_lower) {
            console.getErr().println("空单stop上限[" + _sell_stop_upper + "]低于空单stop下限[" + _sell_stop_lower + "]，请重新设置参数");
            context.stop();
            return;
        }

        /**止损价检查**/
        if (sell_sl > 0 && sell_limit_upper > 0 && sell_limit_upper > sell_sl) {
            console.getErr().println("空单止损价[" + sell_sl + "]低于空单limit上限:[" + sell_limit_upper + "]，请重新设置空单止损价");
            context.stop();
            return;
        }

        if (sell_sl > 0 && sell_stop_upper > 0 && sell_stop_upper > sell_sl) {
            console.getErr().println("空单止损价[" + sell_sl + "]低于空单stop上限:[" + sell_stop_upper + "]，请重新设置空单止损价");
            context.stop();
            return;
        }


        if (buy_sl > 0 && buy_limit_lower > 0 && buy_limit_lower < buy_sl) {
            console.getErr().println("多单止损价[" + buy_sl + "]高于多单limit下限:[" + buy_limit_lower + "]，请重新设置多单止损价");
            context.stop();
            return;
        }

        if (buy_sl > 0 && buy_stop_lower > 0 && buy_stop_lower < buy_sl) {
            console.getErr().println("多单止损价[" + buy_sl + "]高于多单stop下限:[" + buy_stop_lower + "]，请重新设置多单止损价:");
            context.stop();
            return;
        }


        buy_limit_s = initPriceArray(_buy_limit_lower, _buy_limit_upper, jiange_limit);
        buy_stop_s = initPriceArray(_buy_stop_lower, _buy_stop_upper, jiange_stop);
        sell_limit_s = initPriceArray(_sell_limit_lower, _sell_limit_upper, jiange_limit);
        sell_stop_s = initPriceArray(_sell_stop_lower, _sell_stop_upper, jiange_stop);
        if (amount < 1 || type == JingQiType.stopOrder) {
            context.stop();
            return;
        }
        if (type == JingQiType.stopAndCloseOrder) {
            List<IOrder> orderList = engine.getOrders(instrument).stream().filter(x -> x.getLabel().startsWith(LABEL_PRE) && x.getOrderCommand().isConditional()).collect(Collectors.toList());
            orderList.forEach(x -> {
                try {
                    x.close();
                } catch (JFException e) {

                }
            });
            context.stop();
            return;
        }
        console.getInfo().println("JingQi启动,交易品种:"+instrument.getName()+",交易方向:"+type.desc);
    }

    private double[] initPriceArray(double lower, double upper, double jiange_price) {
        double price = lower;
        List<Double> price_List = new ArrayList<>();
        do {
            int a = (int) ((price) / jiange_price);
            double p_f = new BigDecimal(jiange_price * a).setScale(ie.scale, RoundingMode.HALF_UP).doubleValue();
            price_List.add(p_f);
            price += jiange_price;
        } while (price < upper);
        double[] arr = new double[price_List.size()];
        for (int i = 0; i < price_List.size(); i++) {
            arr[i] = price_List.get(i);
        }
        return arr;
    }

    @Override
    public void onBar(Instrument instrument, Period period, OfferSide offerSide, IBar bar) {
        try {
            process();
        } catch (JFException e) {
            return;
        }
    }

    public void process() throws JFException {
        ITimeDomain timeDomain0 = dataService.getOfflineTimeDomain(0);
        ITimeDomain timeDomain1 = dataService.getOfflineTimeDomain(-1);

        if (timeDomain0 == null || timeDomain1 == null) {
            return;
        }
        long time = context.getTime();

        //停盘时间不干活
        if (timeDomain1.getEnd() + period.getInterval() > time || time > timeDomain0.getStart() - period.getInterval()) {
            return;
        }
        ITick tick = history.getLastTick(instrument);

        List<IOrder> orderList = engine.getOrders(instrument);

        List<IOrder> buyList = orderList.stream()
                .filter(x -> x.getLabel().startsWith(LABEL_PRE))
                .filter(x -> x.getOrderCommand().isLong()).collect(Collectors.toList());

        List<IOrder> sellList = orderList.stream()
                .filter(x -> x.getLabel().startsWith(LABEL_PRE))
                .filter(x -> x.getOrderCommand().isShort()).collect(Collectors.toList());

        if (type == JingQiType.both || type == JingQiType.onlyLong) {
            processLong(tick, buyList);
        }
        if (type == JingQiType.both || type == JingQiType.onlyShort) {
            processShort(tick, sellList);
        }
    }

    //做多单
    private void processLong(ITick tick, List<IOrder> orderList) {
        //先做buy stop逻辑
        int buystop_num = 0;
        int buylimit_num = 0;
        for(IOrder order:orderList){
            String label = order.getLabel();
            String[] strs = label.split(EAConstants.split);
            if(strs.length==5 && LABEL_PRE.equals(strs[0]) && order.getState()== IOrder.State.FILLED){
                if("BUYSTOP".equals(strs[3])){
                    buystop_num ++ ;
                }else if("BUYLIMIT".equals(strs[3])){
                    buylimit_num ++ ;
                }
            }
        }

        if(buystop_num < stop_num || !fixed_num){
            for (int i = 1; i < buy_stop_s.length; i++) {
                double b1 = buy_stop_s[i - 1];
                double b0 = buy_stop_s[i];
                if (b1 < tick.getAsk() && tick.getAsk() < b0) {
                    for (int j = 0; j < stop_num && i + j < buy_stop_s.length; j++) {
                        double price = buy_stop_s[i + j];
                        long count = orderList.stream().filter(x -> String.valueOf(price).equals(x.getComment())).count();

                        if (count == 0) {
                            submitOrder(price, IEngine.OrderCommand.BUYSTOP, i + j);
                        }
                    }
                    break;
                }
            }
        }


        //再做buy limit逻辑
        if(buylimit_num < limit_num || !fixed_num){
            for (int i = 1; i < buy_limit_s.length; i++) {
                double b1 = buy_limit_s[i - 1];
                double b0 = buy_limit_s[i];
                if (b1 < tick.getAsk() && tick.getAsk() < b0) {
                    for (int j = 1; j <= limit_num && i - j >= 0; j++) {
                        double price = buy_limit_s[i - j];
                        long count = orderList.stream().filter(x -> String.valueOf(price).equals(x.getComment())).count();
                        if (count == 0) {
                            submitOrder(price, IEngine.OrderCommand.BUYLIMIT, i - j);
                        }
                    }
                    break;
                }
            }
        }

    }

    private void processShort(ITick tick, List<IOrder> orderList) {
        int sellstop_num = 0;
        int selllimit_num = 0;
        for(IOrder order:orderList){
            String label = order.getLabel();
            String[] strs = label.split(EAConstants.split);
            if(strs.length==5 && LABEL_PRE.equals(strs[0]) && order.getState()== IOrder.State.FILLED){
                if("SELLSTOP".equals(strs[3])){
                    sellstop_num ++ ;
                }else if("SELLLIMIT".equals(strs[3])){
                    selllimit_num ++ ;
                }
            }
        }

        //先做sell stop逻辑
        if(sellstop_num < stop_num || !fixed_num){

            for (int i = 1; i < sell_stop_s.length; i++) {
                double b1 = sell_stop_s[i - 1];
                double b0 = sell_stop_s[i];
                if (b1 < tick.getBid() && tick.getBid() < b0) {
                    for (int j = 1; j <= stop_num && i - j >= 0; j++) {
                        double price = sell_stop_s[i - j];
                        long count = orderList.stream().filter(x -> String.valueOf(price).equals(x.getComment())).count();
                        if (count == 0) {
                            submitOrder(price, IEngine.OrderCommand.SELLSTOP, i - j);
                        }
                    }
                    break;
                }
            }
        }
        //再做sell limit逻辑
        if(selllimit_num < limit_num || !fixed_num){

            for (int i = 1; i < sell_limit_s.length; i++) {
                double b1 = sell_limit_s[i - 1];
                double b0 = sell_limit_s[i];
                if (b1 < tick.getBid() && tick.getBid() < b0) {
                    for (int j = 0; j < limit_num && i + j < sell_limit_s.length; j++) {
                        double price = sell_limit_s[i + j];
                        long count = orderList.stream().filter(x -> String.valueOf(price).equals(x.getComment())).count();
                        if (count == 0) {
                            submitOrder(price, IEngine.OrderCommand.SELLLIMIT, i + j);
                        }
                    }
                    break;
                }
            }
        }
    }

    final SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
    final SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private void submitOrder(double price, IEngine.OrderCommand orderCommand, int i) {
        String comment = String.valueOf(price);
        String label = LABEL_PRE + EAConstants.split + instrument.name() + EAConstants.split + dateFormat.format(context.getTime()) + EAConstants.split + orderCommand.name() + EAConstants.split + i;
        double sl = orderCommand.isLong() ? buy_sl : sell_sl;
        double tp = 0;
        if (tp_pip > 0) {
            tp = new BigDecimal(orderCommand.isLong() ? price + tp_pip : price - tp_pip).setScale(ie.scale, RoundingMode.HALF_UP).doubleValue();
        }
        double _amount = ie.minAmount * amount;
        long gtc = context.getTime() + order_time * Period.ONE_MIN.getInterval();
        try {
            engine.submitOrder(label, instrument, orderCommand, _amount, price, 5, sl, tp, gtc, comment);
        } catch (JFException e) {
            console.getErr().println("submit error:" + e.getMessage());
        }
    }

    @Override
    public void onTick(Instrument instrument, ITick tick) throws JFException {

    }

    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {

    }

    @Override
    public void onMessage(IMessage message) throws JFException {
        if (message.getOrder() == null) {
            return;
        }
        IOrder order = message.getOrder();
        if (!order.getLabel().startsWith(LABEL_PRE)) {
            return;
        }
        String comment = order.getComment();
        String label = order.getLabel();
        double sl = order.getStopLossPrice();
        double tp = order.getTakeProfitPrice();
        double amount = order.getAmount();
        double openPrice = order.getOpenPrice();
        switch (message.getType()) {
            case ORDER_SUBMIT_OK:
                if (log_submit) {
                    ITick tick = history.getLastTick(instrument);
                    String str = String.format("提交 %s 订单[%s],当前价[%s],挂单价格[%s],止损[%s],止盈[%s],仓量[%s],comment[%s],gtc=%s",
                            order.getOrderCommand().name(), label, order.getOrderCommand().isLong() ? tick.getBid() : tick.getAsk(), openPrice, sl, tp, amount, comment, dateFormat1.format(order.getGoodTillTime()));
                    console.getOut().println(str);
                }
                break;
            case ORDER_FILL_OK:
                if (log_fill) {
                    String str = String.format("成交 订单[%s],open[%s],止损[%s],止盈[%s],仓量[%s],comment[%s]", label, openPrice, sl, tp, amount, comment);
                    console.getNotif().println(str);
                }
                if(fixed_num){
                    //如果固定挂单数量,那么必须要判断当前订单数量是否已经到达5个，如果已经到达，则取消所有挂单
                    String[] strs = label.split(EAConstants.split);
                    if(strs!=null && strs.length==5 && strs[0].equals(LABEL_PRE)){
                        List<IOrder> sameFillOrderList = findSameOrderNum(instrument,strs[3],IOrder.State.FILLED);
                        if(
                                (strs[3].contains("STOP") && sameFillOrderList.size() >= stop_num) ||
                                        (strs[3].contains("LIMIT") && sameFillOrderList.size() >= limit_num)
                        ){
                            if(log_cancel){
                                console.getOut().println(instrument.name()+":"+strs[3]+"已经超过上限，删除所有同类挂单");
                            }
                            List<IOrder> sameOPENEDList = findSameOrderNum(instrument,strs[3],IOrder.State.OPENED);
                            //删除同类挂单
                            for(IOrder sameOPENOrder:sameOPENEDList){
                                sameOPENOrder.close();
                            }
                        }
                    }
                }
                break;
            case ORDER_CHANGED_OK:
                if(message.getContent()!=null && message.getContent().contains("Overnight") && order.getState()== IOrder.State.FILLED && ie!=null && overnight){
                    double new_tp = new BigDecimal(order.isLong() ? order.getOpenPrice() + tp_pip : order.getOpenPrice() - tp_pip).setScale(ie.scale, RoundingMode.HALF_UP).doubleValue();
                    if(new_tp!=tp){
                        order.setTakeProfitPrice(new_tp);
                        if (log_overnight) {
                            console.getOut().println(order.getLabel()+" move tp from "+tp+" to "+new_tp);
                        }
                    }
                }
                break;
            case ORDER_CLOSE_OK:
                if (log_close) {
                    double profit = order.getProfitLossInUSD();
                    double closePrice = order.getClosePrice();
                    String str = String.format("平仓 订单[%s],成本[%s],平仓[%s],盈利[%s],止损[%s],止盈止盈[%s],仓量[%s],comment[%s]", label, openPrice, closePrice, profit, sl, tp, amount, comment);
                    if (profit > 0) {
                        console.getInfo().println(str);
                    } else if (profit < 0) {
                        console.getErr().println(str);
                    } else if (profit == 0 && closePrice == 0) {
                        if (log_cancel) {
                            String str1 = String.format("订单取消 order[%s],open[%s]", label, openPrice);
                            console.getOut().println(str1);
                        }
                    }
                }
                break;
        }
    }

    //已经成交的订单
    private List<IOrder> findSameOrderNum(Instrument instrument , String orderCommand, IOrder.State state) {
        List<IOrder> sameOrder = new ArrayList<>();
        try {
            List<IOrder> orderList = engine.getOrders(instrument);
            for(IOrder order:orderList){
                if(order.getState() != state){
                    continue;
                }
                String label = order.getLabel();
                String[] strs = label.split(EAConstants.split);
                if(strs==null || strs.length!=5 || !strs[0].equals(LABEL_PRE) || !strs[3].equals(orderCommand)){
                    continue;
                }
                sameOrder.add(order);
            }
            return sameOrder;
        } catch (JFException e) {
            return sameOrder;
        }
    }

    @Override
    public void onAccount(IAccount account) throws JFException {
    }

    @Override
    public void onStop() throws JFException {
        ie = null;
    }

    enum JingQiType {
        onlyLong("只挂多单"),
        onlyShort("只挂空单"),
        both("多空都挂"),
        stopOrder("停止挂单"),
        stopAndCloseOrder("停止挂单并删除订单");
        String desc;

        JingQiType(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return desc;
        }
    }
}
